Initial Spinner Implementation#22207
Conversation
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit a83054e:
|
📊 Bundle size report
🤖 This report was generated against 374dca7b9d662f71a1f81297d7e4f4ea95615645 |
Asset size changesSize Auditor did not detect a change in bundle size for any component! Baseline commit: 374dca7b9d662f71a1f81297d7e4f4ea95615645 (build) |
…c-react into react-spinner-implementation
| disabledTests: [ | ||
| 'component-has-static-classname', | ||
| 'component-has-static-classname-exported', | ||
| 'component-has-static-classnames-object', |
There was a problem hiding this comment.
I believe Spinner should be exporting the classnames object covered by this conformance test.
| return ( | ||
| <slots.root {...slotProps.root}> | ||
| {slots.label && (labelPosition === 'above' || labelPosition === 'before') && <slots.label {...slotProps.label} />} | ||
| {slots.spinner && <slots.spinner {...slotProps.spinner} />} |
There was a problem hiding this comment.
Seems like the spinner slot should be required as it's the core feature of this component. If so, it should be marked as NonNullable in the .types.ts file.
| */ | ||
| export const useSpinner_unstable = (props: SpinnerProps, ref: React.Ref<HTMLElement>): SpinnerState => { | ||
| // Props | ||
| const { appearance = 'primary', labelPosition, size = 'medium', status = 'active' } = props; |
There was a problem hiding this comment.
Does the design have a default labelPosition? Seems like we should probably set one here because with it being undefined like this it will prevent labels from rendering unless a user sets labelPosition.
| export * from './renderSpinner'; | ||
| export * from './useSpinner'; | ||
| export * from './useSpinnerStyles'; | ||
| export * from './DefaultSvg'; |
There was a problem hiding this comment.
What's your thinking about exporting this SVG? My initial reaction is that we should not export it and keep it an internal implementation detail. For example, we might want to move away from using an SVG for the animation.
| /** | ||
| * The status of the Spinner. Defaults to active | ||
| */ | ||
| status?: 'active' | 'inactive'; |
There was a problem hiding this comment.
How is status different from disabled?
| animationTimingFunction: 'linear', | ||
| backgroundColor: 'transparent', | ||
| }, | ||
| [`& > svg > circle`]: { |
There was a problem hiding this comment.
What do you think about selecting elements based on class name rather than tag name?
|
|
||
| // global class for Spinner track | ||
| spinnerTrack: { | ||
| [`& > svg > circle#track`]: { |
There was a problem hiding this comment.
We should not use ids as there may be multiple Spinners on a page and users may reasonably use an id like track. Using an id in a selector also makes styles harder to override because of their higher specificity.
| // modifier class for Spinner track if appearance="inverted" | ||
| spinnerTrackInverted: { | ||
| [`& > svg > circle#track`]: { | ||
| stroke: 'rgba(255,255,255,0.2)', |
There was a problem hiding this comment.
Can we use a token here?
| disabledTests: [ | ||
| 'component-has-static-classname', | ||
| 'component-has-static-classname-exported', | ||
| 'component-has-static-classnames-object', |
There was a problem hiding this comment.
Since you are exporting the classnames object, this test should pass and shouldn't be disabled. Only component-has-static-classname should be disabled.
| spinner: 'fui-Spinner__spinner', | ||
| label: 'fui-Spinner__label', | ||
| }; | ||
|
|
||
| const rvalues = { | ||
| tiny: '9', | ||
| extraSmall: '11', | ||
| small: '13', | ||
| medium: '14.5', | ||
| large: '16.5', | ||
| extraLarge: '18.5', | ||
| huge: '20', | ||
| }; | ||
| // Sizes for the Spinner | ||
| const spinnnerSizes = { | ||
| tiny: '20px', | ||
| extraSmall: '24px', | ||
| small: '28px', | ||
| medium: '32px', | ||
| large: '36px', | ||
| extraLarge: '40px', | ||
| huge: '44px', | ||
| }; | ||
|
|
||
| // Stroke widths for the Spinner | ||
| const spinnerStrokeWidth = { | ||
| sWidth: '2px', | ||
| mWidth: '3px', | ||
| lWidth: '4px', | ||
| }; | ||
|
|
There was a problem hiding this comment.
If these sizes are going to be added to the theme tokens, maybe add a TODO mentioning they should be replaced once the tokens are added.
| animationDuration: '1.5s', | ||
| animationIterationCount: 'infinite', | ||
| animationTimingFunction: 'cubic-bezier(0.33,0,0.67,1)', | ||
| //zIndex: 999, |
There was a problem hiding this comment.
if this isn't needed, might want to remove the comment?
| * The root of the Spinner. | ||
| * The root slot receives the `className` and `style` specified directly on the `<Spinner>`. | ||
| */ | ||
| root: Slot<'div'>; |
There was a problem hiding this comment.
nit: Can you make this NonNullable?
| const styles = useStyles(); | ||
| return ( | ||
| <div className={styles.container}> | ||
| <Spinner labelPosition="before" label={<Label>Label Position Before...</Label>} /> |
| const useStyles = makeStyles({ | ||
| container: { | ||
| '> div': { ...shorthands.padding('20px') }, | ||
| '& label': { display: 'block', marginBottom: '10px' }, |
There was a problem hiding this comment.
I think you want to remove this style from the story as it's affecting the vertical positioning of the labels that are part of Spinner which misaligns the labels.
| import * as React from 'react'; | ||
|
|
||
| export const DefaultSvg = () => ( | ||
| <svg role="progressbar"> |
| // TODO Add things shared between props and state here | ||
| /** | ||
| * The appearance of the Spinner. | ||
| * @defaultValue 'primary' |
There was a problem hiding this comment.
I think these @defaultValue should be @default based on this comment: #22096 (comment)
| <circle className="fui-spinner__Track" /> | ||
| <circle className="fui-spinner__Tail" /> |
There was a problem hiding this comment.
nit:
| <circle className="fui-spinner__Track" /> | |
| <circle className="fui-spinner__Tail" /> | |
| <circle className="fui-Spinner__Track" /> | |
| <circle className="fui-Spinner__Tail" /> |
| // style for label | ||
| spinnerLabel: { | ||
| color: tokens.colorNeutralForeground1, | ||
| }, | ||
|
|
There was a problem hiding this comment.
you can remove this since label by default uses colorNeutralForeground1
| // style for label | |
| spinnerLabel: { | |
| color: tokens.colorNeutralForeground1, | |
| }, |
| * Where the label is positioned relative to the Spinner | ||
| * @default 'after' | ||
| */ | ||
| labelPosition?: 'above' | 'below' | 'before' | 'after'; |
There was a problem hiding this comment.
above and below feel positional - like top and bottom. Consider only before and after and then a separate property about horizontal or vertical layout.
There was a problem hiding this comment.
This was discussed in the Spec PR to keep it this way to align with react-positioning: #21336 (comment)
| * Spinner Props | ||
| */ | ||
| export type SpinnerProps = ComponentProps<SpinnerSlots> & SpinnerCommons; | ||
| export type SpinnerProps = Omit<ComponentProps<SpinnerSlots>, 'size'> & Partial<SpinnerCommons>; |
There was a problem hiding this comment.
I think it is better to have commons have the required/optional based on its usage with SpinnerProps. Then have SpinnerState make required/optional changes. This way a caller reading the code doesn't have to do mental gymnastics to figure out what is required/option in props.
|
|
||
| // modifier class for Spinner trail | ||
| spinnerTailInverted: { | ||
| [`& > svg > circle.fui-spinner__Tail`]: { |
There was a problem hiding this comment.
Nit: You should use regular single quote instead of string template quotes unless you are injecting a value.
| }, | ||
| animationDuration: '1.5s', | ||
| animationIterationCount: 'infinite', | ||
| animationTimingFunction: 'cubic-bezier(0.33,0,0.67,1)', |
There was a problem hiding this comment.
Consider referencing constants like you do for body1 unless the tokens are now available.
| disabledTests: ['component-has-static-classname', 'component-has-static-classname-exported'], | ||
| }); | ||
|
|
||
| // TODO add more tests here, and create visual regression tests in /apps/vr-tests |
There was a problem hiding this comment.
Consider adding the it('renders') test maybe with and without the label.
| * TODO: Update with proper tokens when added | ||
| * Radii for the Spinner circles | ||
| */ | ||
| const rvalues = { |

This PR is for the initial implementation of the v9 Spinner
Related Issue(s)
Fixes #21399